/**
 * @description 主入口
 *
 * @author xiaomi
 */
'use strict';

// env
require('../env');

// deps
const koa = require('koa');
const path = require('path');
const Checkit = require('checkit');
const favicon = require('koa-favicon');
const onerror = require('koa-onerror');
const compose = require('koa-compose');
const session = require('koa-generic-session');
const serve = require('koa-static');

// logger
const logger = require('./services/logger');
const exitHandler = require('./services/exit-handler');

// init app, whit proxy
const app = koa();
app.proxy = true;

// 404
app.use(function *(next) {
    yield * next;

    if(this.status === 404 && this.body === undefined) {
        this.throw(404);
    }
});

app.use(serve(path.join(process.cwd(), '/dist-views/admin')));

// db pagination middle ware
const db = require('./db');
app.use(db.paginationMiddleWare());

// session
app.keys = [process.env.APP_KEY];
app.use(session({
    store: require('./services/redis'),
    key: 'xsb.sid',
    cookie: {
        maxage: 365 * 24 * 60 * 60 * 1000
    }
}));

// innerApps
const innerApps = {
    [process.env.ADMIN_HOST]: compose(require('./admin').middleware),
    [process.env.WWW_HOST]: compose(require('./www').middleware),
    [process.env.API_HOST.split(':')[0]]: compose(require('./api').middleware)
};
const defaultInnerApp = innerApps[process.env.ADMIN_HOST];

app.use(function *(next) {
    const innerApp = innerApps[this.hostname] || defaultInnerApp;

    yield innerApp.call(this, next);
});

// favicon
// maxAge, 1 month
app.use(favicon(path.join(__dirname, '../favicon.ico'), {
    maxAge: 30 * 24 * 60 * 60 * 1000
}));

// Error handle
app.handleError = function(err, ctx) {
    let data = err.data || {};
    let statusCode = err.status;

    // intercept 404
    if(err instanceof db.Model.NotFoundError) {
        statusCode = 404;
    }

    // CheckIt to 400
    if(err instanceof Checkit.Error) {
        data.message = err.toString(true);
        statusCode = 400;
    }

    ctx.status = statusCode || 500;
    data.status = ctx.status;

    if(!data.message) {
        data.message = err.message;
    }

    if(app.env === 'development') {
        data.stack = err.stack.split('\n');
    }

    return data;
};
onerror(app, {
    template: path.join(__dirname, '../views/www/error.html'),
    accepts() {
        let type = this.accepts(['json', 'html']);

        if(type !== 'html') {
            type = 'json';
        }

        return type;
    },
    json(err) {
        let data = app.handleError(err, this);

        this.body = data;
    },
    html(err) {
        let data = app.handleError(err, this);

        // this.body = this.render('error', {
        //     env: process.env,
        //     error: err.message,
        //     status: this.status,
        //     code: err.code,
        //     data: data
        // });

        this.body = `<!DOCTYPE html><html>
            <head><meta charset="UTF-8"/><title>${err.message}-${this.status}</title></head><body>
            <h1>${err.message}-${this.status}</h1><p><pre>${JSON.stringify(data, null, 2)}</pre></p>
            </body></html>`;
    }
});

// Error report
app.on('error', (err, ctx) => {
    let meta = err.data;
    // axios request error
    let response = err.response;
    if(response) {
        meta = response.data;

        if(meta) {
            err.message += ': ' + meta.message;
        }
    }

    err.data = {
        url: ctx.url,
        method: ctx.method,
        query: ctx.request.query,
        body: ctx.request.body ? JSON.stringify(ctx.request.body) : null,
        code: err.code,
        status: err.status || err.statusCode || ctx.status,
        meta: meta ? JSON.stringify(meta) : null,
        referer: ctx.get('Referer'),
        ua: ctx.get('User-Agent'),
        ip: ctx.ip
    };
    logger.error(err, err.data);
});

// defalut exit handler
const exitWithCleanupHandler = exitHandler.bind(null, {
    cleanup() {
        return new Promise((resolve, reject) => {
            if(!db && !db.knex) {
                return resolve();
            }

            db.knex.destroy(err => {
                if(err) {
                    return reject(err);
                }

                resolve();
            });
        });
    }
});

// process crash
process.on('uncaughtException', exitWithCleanupHandler);

// Ctrl+C
process.on('SIGINT', exitWithCleanupHandler);

// // catches "kill pid" (for example: nodemon restart)
// process.on('SIGUSR1', exitWithCleanupHandler);
// process.on('SIGUSR2', exitWithCleanupHandler);

// exit event
process.on('exit', exitWithCleanupHandler);


// start up
app.port = process.env.PORT || 3002;
app.listen(app.port, () => {
    // ready message
    if(process.send) {
        process.send('ready');
    }

    logger.info('Server Start...', {
        port: app.port,
        admin: 'http://' + process.env.ADMIN_HOST,
        www: 'http://' + process.env.WWW_HOST,
        api: 'http://' + process.env.API_HOST
    });
});

// exports
module.exports = app;
